﻿using SharpDX.D3DCompiler;
using SharpDX.Direct3D11;
using System;
using System.IO;

namespace Engine.Graphics.Shaders
{
    public class IncludeFX : Include
    {
        #region Private Fields

        private static string includeDirectory = ".\\Shaders\\";
        private System.IO.Stream stream;

        #endregion Private Fields

        #region Public Properties

        public IDisposable Shadow
        {
            get
            {
                return this;
            }
            set
            {
            }
        }

        #endregion Public Properties

        #region Public Methods

        public void Close(System.IO.Stream stream)
        {
            stream.Close();
            stream.Dispose();
        }

        public void Dispose()
        {
            this.stream.Close();
            this.stream.Dispose();
            this.stream = null;
        }

        public void Open(IncludeType type, string fileName, System.IO.Stream parentStream, out System.IO.Stream stream)
        {
            stream = new System.IO.FileStream(includeDirectory + fileName, System.IO.FileMode.Open);
        }

        public System.IO.Stream Open(IncludeType type, string fileName, System.IO.Stream parentStream)
        {
            return stream = new System.IO.FileStream(includeDirectory + fileName, System.IO.FileMode.Open);
        }

        #endregion Public Methods
    }

    public class ShaderBase
    {
        #region Public Fields

        public InputLayout inputLayout;
        public PixelShader ps;
        public ShaderBytecode shaderByteCodePS;
        public ShaderBytecode shaderByteCodeVS;
        public VertexShader vs;
        private ISetBlendStates blendStatesObject;
        private ISetSamplerStates samplerStateObject;

        #endregion Public Fields

        #region Internal Fields

        public static uint lastHashcodeMappingMatrix1 = 0;
        public static uint lastHashcodeMappingMatrix2 = 0;
        public static uint lastHashcodeMappingMatrix3 = 0;
        public Device Device { get; set; }

        #endregion Internal Fields

        #region Private Fields

        public static void SetSamplerState(int slot, SamplerState samplerState, Device device)
        {
            if (lastHashSamplerStates[slot] != samplerState.GetHashCode())
            {
                lastHashSamplerStates[slot] = samplerState.GetHashCode();
                lock (device.ImmediateContext)
                    device.ImmediateContext.PixelShader.SetSampler(slot, samplerState);
            }
        }

        private static readonly int[] lastHashSamplerStates = new int[16];
        private static readonly int[] lastHashTextures = new int[16];
        public ShaderManager shaderManager;

        #endregion Private Fields

        public void SetBlendStateObject(ISetBlendStates blendstateObject)
        {
            this.blendStatesObject = blendstateObject;
        }

        public void SetSamplerStateObject(ISetSamplerStates samplerStateObject)
        {
            this.samplerStateObject = samplerStateObject;
        }

        #region Public Constructors

        public ShaderBase(Device device)
        {
            this.Device = device;
        }

        public ShaderBase(Device renderDevice, ShaderManager shaderManager)
            : this(renderDevice)
        {
            this.shaderManager = shaderManager;
        }

        #endregion Public Constructors

        #region Public Properties

        public ShaderBase effect { get { return this; } }

        #endregion Public Properties

        #region Public Methods

        public void LoadShader(Device device, string fileName, string PixelShaderName, string VertexShaderName)
        {
            Helpers.LogWriter.Write(0, "Load Shader: " + fileName);
            ShaderFlags shaderflags = ShaderFlags.OptimizationLevel3;

#if DEBUG
            shaderflags = ShaderFlags.Debug | ShaderFlags.SkipOptimization;
#else
            shaderflags = ShaderFlags.OptimizationLevel3;
#endif
            if (fileName.StartsWith("."))
                fileName = fileName.Substring(1, fileName.Length - 1);

            string filename = Engine.GetStartupPath() + Path.GetDirectoryName(fileName) + "\\" + Path.GetFileNameWithoutExtension(fileName);

            filename = filename.Replace("\\\\", "\\"); //string pathWithoutExtension = Path.GetDirectoryName(fileName) + "\\" + Path.GetFileNameWithoutExtension(fileName);
            string pathWithoutExtension = filename.ToString();
            string filenameVSCompiles = pathWithoutExtension + "VS.CSO";
            string filenamePSCompiles = pathWithoutExtension + "PS.CSO";
            if (File.Exists(filenameVSCompiles)) // Load VertexShader
            {
                shaderByteCodeVS = ShaderBytecode.FromFile(filenameVSCompiles);
            }
            else
            {
                IncludeFX include = new IncludeFX();
                shaderByteCodeVS = ShaderBytecode.CompileFromFile(fileName, VertexShaderName, "vs_4_0", shaderflags, EffectFlags.None, null, include);
            }

            if (File.Exists(filenamePSCompiles)) // Load PixelShader
            {
                shaderByteCodePS = ShaderBytecode.FromFile(filenamePSCompiles);
            }
            else
            {
                IncludeFX include = new IncludeFX();
                shaderByteCodePS = ShaderBytecode.CompileFromFile(fileName, PixelShaderName, "ps_4_0", shaderflags, EffectFlags.None, null, include);
            }
            try // Vertexshader
            {
                vs = new VertexShader(device, shaderByteCodeVS.Data);
                this.vs.DebugName = filenameVSCompiles;
            }
            catch (Exception ex)
            {
                Helpers.LogWriter.Write(100, "[VS failed] " + fileName + "\n" + ex.Message);
            }

            try
            {
                ps = new PixelShader(device, shaderByteCodePS.Data);
                this.ps.DebugName = filenamePSCompiles;
            }
            catch (Exception ex)
            {
                Helpers.LogWriter.Write(100, "[PS failed] " + fileName + "\n" + ex.Message);
            }
        }

        public void SetSamplerState(SamplerState samplerState, int slot)
        {
            if (lastHashSamplerStates[slot] != samplerState.GetHashCode())
            {
                this.Device.ImmediateContext.PixelShader.SetSampler(slot, samplerState);
                lastHashSamplerStates[slot] = samplerState.GetHashCode();
            }
        }

        public static void SetTexture(int slot, ShaderResourceView resource, Device device)
        {
            if (resource != null)
            {
                if (ShaderBase.lastHashTextures[slot] != resource.GetHashCode())
                {
                    ShaderBase.lastHashTextures[slot] = resource.GetHashCode();
                    lock (device.ImmediateContext)
                        device.ImmediateContext.PixelShader.SetShaderResource(slot, resource);
                }
            }
            else
            {
                lock (device.ImmediateContext)
                    device.ImmediateContext.PixelShader.SetShaderResource(slot, null);
            }
        }

        #endregion Public Methods

        #region Internal Methods

        internal static void ResetShaderValues()
        {
            ShaderBase.lastHashcodeMappingMatrix1 = 0;
            ShaderBase.lastHashcodeMappingMatrix2 = 0;
            ShaderBase.lastHashcodeMappingMatrix3 = 0;

            for (int i = 0; i < ShaderBase.lastHashSamplerStates.Length; i++)
            {
                ShaderBase.lastHashSamplerStates[i] = -1;
            }

            for (int i = 0; i < ShaderBase.lastHashTextures.Length; i++)
            {
                ShaderBase.lastHashTextures[i] = -1;
            }
        }

        public void Dispose()
        {
            if (this.inputLayout != null)
                this.inputLayout.Dispose();
            this.inputLayout = null;
            if (this.ps != null)
                this.ps.Dispose();
            this.ps = null;
            if (this.vs != null)
                this.vs.Dispose();
            this.vs = null;
            if (this.shaderByteCodeVS != null)
                this.shaderByteCodeVS.Dispose();
            this.shaderByteCodeVS = null;
            if (this.shaderByteCodePS != null)
                this.shaderByteCodePS.Dispose();
            this.shaderByteCodePS = null;
        }

        #endregion Internal Methods

        internal void SetSamplerStates()
        {
            if (this.samplerStateObject != null)
                this.samplerStateObject.SetSamplerStates();
        }

        internal void SetBlendStates()
        {
            if (this.blendStatesObject != null)
                this.blendStatesObject.SetBlendStates();
        }
    }
}